To illustrate the basic process of reflection (and the usefulness of System.Type), let’s create a Console Application named MyTypeViewer. This program will display details of the methods, properties, fields, and supported interfaces (in addition to some other points of interest) for any type within mscorlib.dll (recall all .NET applications have automatic access to this core framework class library) or a type within MyTypeViewer itself. Once the application has been created, be sure to import the System.Reflection namespace.
// Need to import this namespace to do any reflection! using System.Reflection;
The Program class will be updated to define a number of static methods, each of which takes a single System.Type parameter and returns void. First you have ListMethods(), which (as you might guess) prints the name of each method defined by the incoming type. Notice how Type.GetMethods() returns an array of System.Reflection.MethodInfo objects, which can be enumerated over using a standard foreach loop:
// Display method names of type. static void ListMethods(Type t) { Console.WriteLine("***** Methods *****"); MethodInfo[] mi = t.GetMethods(); foreach(MethodInfo m in mi) Console.WriteLine("->{0}", m.Name); Console.WriteLine(); }
Here, you are simply printing the name of the method using the MethodInfo.Name property. As you might guess, MethodInfo has many additional members that allow you to determine whether the method is static, virtual, generic, or abstract. As well, the MethodInfo type allows you to obtain the method’s return value and parameter set. You’ll spruce up the implementation of ListMethods() in just a bit.
If you wish, you could also build a fitting LINQ query to enumerate the names of each method. Recall from Chapter 13, LINQ to Objects allows you to build strongly typed queries which can be applied to in-memory object collections. As a good rule of thumb, whenever you find blocks of looping or decision programming logic, you could make use of a related LINQ query. For example, you could rewrite the previous method as so:
static void ListMethods(Type t) { Console.WriteLine("***** Methods *****"); var methodNames = from n in t.GetMethods() select n.Name; foreach (var name in methodNames) Console.WriteLine("->{0}", name); Console.WriteLine(); }
The implementation of ListFields() is similar. The only notable difference is the call to Type.GetFields() and the resulting FieldInfo array. Again, to keep things simple, you are printing out only the name of each field using a LINQ query.
// Display field names of type. static void ListFields(Type t) { Console.WriteLine("***** Fields *****"); var fieldNames = from f in t.GetFields() select f.Name; foreach (var name in fieldNames) Console.WriteLine("->{0}", name); Console.WriteLine(); }
The logic to display a type’s properties is similar as well.
// Display property names of type. static void ListProps(Type t) { Console.WriteLine("***** Properties *****"); var propNames = from p in t.GetProperties() select p.Name; foreach (var name in propNames) Console.WriteLine("->{0}", name); Console.WriteLine(); }
Next, you will author a method named ListInterfaces() that will print out the names of any interfaces supported on the incoming type. The only point of interest here is that the call to GetInterfaces() returns an array of System.Types! This should make sense given that interfaces are, indeed, types:
// Display implemented interfaces. static void ListInterfaces(Type t) { Console.WriteLine("***** Interfaces *****"); var ifaces = from i in t.GetInterfaces() select i; foreach(Type i in ifaces) Console.WriteLine("->{0}", i.Name); }
Note Be aware that a majority of the “get” methods of System.Type (GetMethods(), GetInterfaces(), etc) have been overloaded to allow you to specify values from the BindingFlags enumeration. This provides a greater level of control on exactly what should be searched for (e.g., only static members, only public members, include private members, etc). Consult the .NET Framework 4.0 SDK documentation for details.
Last but not least, you have one final helper method that will simply display various statistics (indicating whether the type is generic, what the base class is, whether the type is sealed, and so forth) regarding the incoming type:
// Just for good measure. static void ListVariousStats(Type t) { Console.WriteLine("***** Various Statistics *****"); Console.WriteLine("Base class is: {0}", t.BaseType); Console.WriteLine("Is type abstract? {0}", t.IsAbstract); Console.WriteLine("Is type sealed? {0}", t.IsSealed); Console.WriteLine("Is type generic? {0}", t.IsGenericTypeDefinition); Console.WriteLine("Is type a class type? {0}", t.IsClass); Console.WriteLine(); }
The Main() method of the Program class prompts the user for the fully qualified name of a type. Once you obtain this string data, you pass it into the Type.GetType() method and send the extracted System.Type into each of your helper methods. This process repeats until the user enters Q to terminate the application:
static void Main(string[] args) { Console.WriteLine("***** Welcome to MyTypeViewer *****"); string typeName = ""; do { Console.WriteLine("\nEnter a type name to evaluate"); Console.Write("or enter Q to quit: "); // Get name of type. typeName = Console.ReadLine(); // Does user want to quit? if (typeName.ToUpper() == "Q") { break; } // Try to display type. try { Type t = Type.GetType(typeName); Console.WriteLine(""); ListVariousStats(t); ListFields(t); ListProps(t); ListMethods(t); ListInterfaces(t); } catch { Console.WriteLine("Sorry, can't find type"); } } while (true); }
At this point, MyTypeViewer.exe is ready to take out for a test drive. For example, run your application and enter the following fully qualified names (be aware that the manner in which you invoked Type.GetType() requires case-sensitive string names):
For example, here is some partial output when specifying System.Math.
***** Welcome to MyTypeViewer ***** Enter a type name to evaluate or enter Q to quit: System.Math ***** Various Statistics ***** Base class is System.Object Is type abstract? True Is type sealed? True Is type generic? False Is type a class type? True ***** Fields ***** ->PI ->E ***** Properties ***** ***** Methods ***** ->Acos ->Asin ->Atan ->Atan2 ->Ceiling ->Ceiling ->Cos ...
When you call Type.GetType() in order to obtain metadata descriptions of generic types, you must make use of a special syntax involving a “back tick” character (`) followed by a numerical value that represents the number of type parameters the type supports. For example, if you wish to print out the metadata description of System.Collections.Generic.List<T>, you would need to pass the following string into your application:
System.Collections.Generic.List`1
Here, you are using the numerical value of 1, given that List<T> has only one type parameter. However, if you wish to reflect over Dictionary<TKey, TValue>, you would supply the value 2:
System.Collections.Generic.Dictionary`2
So far, so good! Let’s make a minor enhancement to the current application. Specifically, you will update the ListMethods() helper function to list not only the name of a given method, but also the return type and incoming parameter types. The MethodInfo type provides the ReturnType property and GetParameters() method for these very tasks. In the following modified code, notice that you are building a string that contains the type and name of each parameter using a nested foreach loop (without the use of LINQ):
static void ListMethods(Type t) { Console.WriteLine("***** Methods *****"); MethodInfo[] mi = t.GetMethods(); foreach (MethodInfo m in mi) { // Get return type. string retVal = m.ReturnType.FullName; string paramInfo = "( "; // Get params. foreach (ParameterInfo pi in m.GetParameters()) { paramInfo += string.Format("{0} {1} ", pi.ParameterType, pi.Name); } paramInfo += " )"; // Now display the basic method sig. Console.WriteLine("->{0} {1} {2}", retVal, m.Name, paramInfo); } Console.WriteLine(); }
If you now run this updated application, you will find that the methods of a given type are much more detailed. If you enter your good friend, System.Object as input to the program, the following methods will display:
***** Methods ***** ->System.String ToString ( ) ->System.Boolean Equals ( System.Object obj ) ->System.Boolean Equals ( System.Object objA System.Object objB ) ->System.Boolean ReferenceEquals ( System.Object objA System.Object objB ) ->System.Int32 GetHashCode ( ) ->System.Type GetType ( )
The current implementation of ListMethods() is helpful, in that you can directly investigate each parameter and method return type using the System.Reflection object model. As an extreme shortcut, be aware that each of the XXXInfo types (MethodInfo, PropertyInfo, EventInfo, etc.) have overridden ToString() to display the signature of the item requested. Thus, you could also implement ListMethods() as follows (once again using LINQ, where you simply select all MethodInfo objects, rather than only the Name values):
static void ListMethods(Type t) { Console.WriteLine("***** Methods *****"); var methodNames = from n in t.GetMethods() select n; foreach (var name in methodNames) Console.WriteLine("->{0}", name); Console.WriteLine(); }
Interesting stuff, huh? Clearly the System.Reflection namespace and System.Type class allow you to reflect over many other aspects of a type beyond what MyTypeViewer is currently displaying. As you would hope, you can obtain a type’s events, get the list of any generic parameters for a given member, and glean dozens of other details.
Nevertheless, at this point you have created a (somewhat capable) object browser. The major limitation, of course, is that you have no way to reflect beyond the current assembly (MyTypeViewer) or the always accessible mscorlib.dll. This begs the question, “How can I build applications that can load (and reflect over) assemblies not referenced at compile time?” Glad you asked.
Source Code The MyTypeViewer project can be found under the Chapter 15 subdirectory.